<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>轻量语音识别转文本</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/static/styles.css">
    <style>
        :root {
            --primary-color: #3f51b5;
            --primary-light: #e8eaf6;
            --primary-dark: #303f9f;
            --accent-color: #ff4081;
            --success-color: #4caf50;
            --warning-color: #ff9800;
            --error-color: #f44336;
            --text-primary: #212121;
            --text-secondary: #757575;
            --text-disabled: #9e9e9e;
            --background-color: #f9f9f9;
            --surface-color: #ffffff;
            --shadow-color: rgba(0, 0, 0, 0.1);
            --border-radius: 12px;
            --transition-speed: 0.3s;
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
            margin: 0;
            padding: 0;
            background-color: var(--background-color);
            color: var(--text-primary);
            line-height: 1.6;
        }

        .container {
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
        }

        .header {
            text-align: center;
            margin-bottom: 30px;
            position: relative;
            padding-bottom: 15px;
        }

        .header h1 {
            font-size: 2.2rem;
            font-weight: 700;
            color: var(--primary-color);
            margin-bottom: 5px;
            text-shadow: 1px 1px 2px var(--shadow-color);
        }

        .header::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 80px;
            height: 3px;
            background-color: var(--primary-color);
            border-radius: 3px;
        }

        .chat-container {
            background-color: var(--surface-color);
            border-radius: var(--border-radius);
            box-shadow: 0 4px 20px var(--shadow-color);
            height: 500px;
            overflow-y: auto;
            padding: 25px;
            margin-bottom: 20px;
            transition: all var(--transition-speed);
            scroll-behavior: smooth;
        }

        .chat-container:hover {
            box-shadow: 0 6px 25px rgba(0, 0, 0, 0.15);
        }

        .message {
            margin-bottom: 18px;
            padding: 12px 16px;
            border-radius: 18px;
            max-width: 85%;
            word-wrap: break-word;
            position: relative;
            animation: fadeIn 0.3s ease-out;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .user-message {
            background-color: var(--primary-light);
            color: var(--text-primary);
            margin-left: auto;
            border-bottom-right-radius: 5px;
            box-shadow: 0 2px 5px rgba(63, 81, 181, 0.2);
        }

        .partial-message {
            background-color: rgba(232, 234, 246, 0.7);
            color: var(--text-secondary);
            margin-left: auto;
            border-bottom-right-radius: 5px;
            opacity: 0.8;
            font-style: italic;
            transition: all 0.2s;
        }

        .assistant-message {
            background-color: #f5f5f5;
            color: var(--text-primary);
            margin-right: auto;
            border-bottom-left-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08);
        }

        .system-message {
            background-color: #fff3e0;
            color: #e65100;
            margin: 0 auto;
            text-align: center;
            font-style: italic;
            border-radius: 20px;
            max-width: 70%;
            font-size: 0.9rem;
        }

        .message-time {
            font-size: 10px;
            color: var(--text-disabled);
            position: absolute;
            bottom: -5px;
            right: 10px;
        }

        .status {
            text-align: center;
            margin: 15px auto 25px;
            font-style: italic;
            color: var(--text-secondary);
            padding: 10px 15px;
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 30px;
            box-shadow: 0 2px 8px var(--shadow-color);
            max-width: 80%;
            transition: all var(--transition-speed);
        }

        .controls {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 30px;
            margin-top: 30px;
        }

        .mic-button {
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 50%;
            width: 75px;
            height: 75px;
            font-size: 30px;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: 0 4px 10px rgba(63, 81, 181, 0.3);
        }

        .mic-button:hover {
            background-color: var(--primary-dark);
            transform: scale(1.05);
            box-shadow: 0 6px 15px rgba(63, 81, 181, 0.4);
        }

        .mic-button.listening {
            background-color: var(--error-color);
            animation: pulse 1.5s infinite;
        }

        .button-text {
            font-size: 14px;
            margin-top: 8px;
            color: var(--text-secondary);
            text-align: center;
            font-weight: 500;
        }

        .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .clear-button {
            background-color: var(--warning-color);
            color: white;
            border: none;
            border-radius: 50%;
            width: 55px;
            height: 55px;
            font-size: 22px;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: 0 3px 8px rgba(255, 152, 0, 0.3);
        }

        .clear-button:hover {
            background-color: #f57c00;
            transform: scale(1.05);
            box-shadow: 0 5px 12px rgba(255, 152, 0, 0.4);
        }

        .save-button {
            background-color: #2196F3;
            color: white;
            border: none;
            border-radius: 50%;
            width: 55px;
            height: 55px;
            font-size: 22px;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: 0 3px 8px rgba(33, 150, 243, 0.3);
        }

        .save-button:hover {
            background-color: #1976D2;
            transform: scale(1.05);
            box-shadow: 0 5px 12px rgba(33, 150, 243, 0.4);
        }

        @keyframes pulse {
            0% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.5);
            }
            70% {
                transform: scale(1.05);
                box-shadow: 0 0 0 15px rgba(244, 67, 54, 0);
            }
            100% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(244, 67, 54, 0);
            }
        }

        .status-indicator {
            display: inline-block;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            margin-right: 8px;
            vertical-align: middle;
        }

        .status-active {
            background-color: var(--success-color);
            box-shadow: 0 0 8px var(--success-color);
        }

        .status-inactive {
            background-color: #ccc;
        }

        .status-listening {
            background-color: var(--error-color);
            animation: blink 1.5s infinite;
            box-shadow: 0 0 8px var(--error-color);
        }

        @keyframes blink {
            0% { opacity: 1; }
            50% { opacity: 0.5; }
            100% { opacity: 1; }
        }

        #partialContainer {
            position: relative;
            min-height: 40px;
            margin-bottom: 15px;
            transition: all 0.3s;
        }

        .connection-status {
            position: absolute;
            top: 10px;
            right: 10px;
            font-size: 12px;
            color: var(--text-secondary);
            background-color: rgba(255, 255, 255, 0.9);
            padding: 6px 12px;
            border-radius: 20px;
            box-shadow: 0 2px 5px var(--shadow-color);
            transition: all 0.3s;
        }

        .connected {
            color: var(--success-color);
            font-weight: 500;
        }

        .disconnected {
            color: var(--error-color);
            font-weight: 500;
        }

        .empty-state {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: var(--text-disabled);
            font-style: italic;
            text-align: center;
        }

        .empty-state-icon {
            font-size: 40px;
            margin-bottom: 20px;
            opacity: 0.5;
        }

        .empty-state-text {
            font-size: 16px;
            max-width: 80%;
        }

        /* 适配不同屏幕尺寸 */
        @media (max-width: 768px) {
            .container {
                padding: 15px;
            }
            .chat-container {
                height: 400px;
                padding: 15px;
            }
            .message {
                max-width: 90%;
            }
            .controls {
                gap: 20px;
            }
            .mic-button {
                width: 65px;
                height: 65px;
                font-size: 26px;
            }
            .clear-button, .save-button {
                width: 45px;
                height: 45px;
                font-size: 18px;
            }
            .header h1 {
                font-size: 1.8rem;
            }
        }

        @media (max-width: 480px) {
            .container {
                padding: 10px;
            }
            .header h1 {
                font-size: 1.5rem;
            }
            .chat-container {
                height: 350px;
                padding: 12px;
            }
            .controls {
                gap: 15px;
            }
            .system-message {
                max-width: 90%;
            }
        }

        /* 按钮动画效果 */
        button {
            position: relative;
            overflow: hidden;
        }

        button::after {
            content: '';
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 50%;
            transform: scale(0);
            opacity: 0;
            transition: transform 0.3s, opacity 0.3s;
        }

        button:active::after {
            transform: scale(2);
            opacity: 0;
            transition: 0s;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>轻量语音识别转文本</h1>
            <div class="connection-status" id="connectionStatus">
                <span class="status-indicator status-inactive" id="connectionIndicator"></span>
                <span id="connectionText">正在连接...</span>
            </div>
        </div>
        
        <div class="chat-container" id="chatContainer">
            <div class="empty-state" id="emptyState">
                <div class="empty-state-icon">🎤</div>
                <div class="empty-state-text">点击下方麦克风按钮开始语音识别</div>
            </div>
        </div>
        
        <div id="partialContainer"></div>
        
        <div class="status" id="statusText">
            <span class="status-indicator status-inactive" id="statusIndicator"></span>
            <span id="statusMessage">准备就绪，点击麦克风开始识别</span>
        </div>
        
        <div class="controls">
            <div class="control-group">
                <button class="mic-button" id="micButton">
                    <i>🎤</i>
                </button>
                <div class="button-text">开始/停止识别</div>
            </div>
            
            <div class="control-group">
                <button class="clear-button" id="clearButton">
                    <i>🗑️</i>
                </button>
                <div class="button-text">清空记录</div>
            </div>
            
            <div class="control-group">
                <button class="save-button" id="saveButton">
                    <i>💾</i>
                </button>
                <div class="button-text">保存记录</div>
            </div>
        </div>
    </div>

    <script>
        // 状态变量
        let isListening = false;
        let socket = null;
        let reconnectAttempts = 0;
        let reconnectInterval = 1000; // 初始重连间隔 1 秒
        const maxReconnectInterval = 30000; // 最大重连间隔 30 秒
        let messageCount = 0;
        
        // DOM 元素
        const chatContainer = document.getElementById('chatContainer');
        const partialContainer = document.getElementById('partialContainer');
        const statusIndicator = document.getElementById('statusIndicator');
        const statusMessage = document.getElementById('statusMessage');
        const micButton = document.getElementById('micButton');
        const clearButton = document.getElementById('clearButton');
        const saveButton = document.getElementById('saveButton');
        const connectionIndicator = document.getElementById('connectionIndicator');
        const connectionText = document.getElementById('connectionText');
        const emptyState = document.getElementById('emptyState');
        
        // 添加按钮音效
        function createAudio(src) {
            const audio = new Audio();
            audio.src = src;
            audio.volume = 0.5;
            return audio;
        }
        
        // 使用内置声音效果
        const clickSound = {
            play: function() {
                try {
                    const context = new (window.AudioContext || window.webkitAudioContext)();
                    const oscillator = context.createOscillator();
                    const gain = context.createGain();
                    
                    oscillator.connect(gain);
                    gain.connect(context.destination);
                    
                    oscillator.type = 'sine';
                    oscillator.frequency.value = 800;
                    gain.gain.value = 0.1;
                    
                    oscillator.start(0);
                    
                    gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.1);
                    oscillator.stop(context.currentTime + 0.1);
                } catch(e) {
                    console.log('音效播放失败', e);
                }
            }
        };
        
        // 建立WebSocket连接
        function connectWebSocket() {
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsUrl = `${protocol}//${window.location.host}/ws`;
            
            console.log('尝试连接到:', wsUrl);
            
            // 关闭之前的连接
            if (socket) {
                socket.close();
            }
            
            try {
                // 创建新的连接
                socket = new WebSocket(wsUrl);
                
                // 连接建立时的处理
                socket.onopen = function(e) {
                    console.log('WebSocket连接已建立');
                    connectionIndicator.className = 'status-indicator status-active';
                    connectionText.textContent = '已连接';
                    connectionText.className = 'connected';
                    reconnectAttempts = 0;
                    reconnectInterval = 1000;
                    updateStatus('inactive', '准备就绪，点击麦克风开始识别');
                };
                
                // 收到消息时的处理
                socket.onmessage = function(event) {
                    try {
                        const data = JSON.parse(event.data);
                        console.log('收到WebSocket消息:', data);
                        
                        if (data.type === 'status') {
                            updateStatus(data.status, data.message);
                        } 
                        else if (data.type === 'message') {
                            if (data.source === 'user') {
                                addMessage(data.text, 'user');
                            } else if (data.source === 'system') {
                                addMessage(data.text, 'system');
                            } else if (data.source === 'partial') {
                                addMessage(data.text, 'partial');
                            }
                        }
                    } catch (error) {
                        console.error('处理WebSocket消息时出错:', error);
                    }
                };
                
                // 连接关闭时的处理
                socket.onclose = function(event) {
                    connectionIndicator.className = 'status-indicator status-inactive';
                    connectionText.textContent = '已断开';
                    connectionText.className = 'disconnected';
                    
                    if (!event.wasClean) {
                        console.log('WebSocket连接意外断开，尝试重连...');
                        scheduleReconnect();
                    } else {
                        console.log(`WebSocket连接已关闭，代码=${event.code}`);
                    }
                };
                
                // 连接错误时的处理
                socket.onerror = function(error) {
                    console.error('WebSocket错误:', error);
                    updateStatus('inactive', '连接错误');
                    scheduleReconnect();
                };
            } catch (error) {
                console.error('创建WebSocket连接时出错:', error);
                scheduleReconnect();
            }
        }
        
        // 安排重连
        function scheduleReconnect() {
            console.log(`${reconnectInterval / 1000} 秒后尝试重连...`);
            setTimeout(function() {
                reconnectAttempts++;
                connectWebSocket();
                // 指数退避策略
                reconnectInterval = Math.min(reconnectInterval * 1.5, maxReconnectInterval);
            }, reconnectInterval);
        }
        
        // 获取当前时间
        function getCurrentTime() {
            const now = new Date();
            return now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
        }
        
        // 检查是否需要显示空状态
        function checkEmptyState() {
            if (messageCount === 0) {
                emptyState.style.display = 'flex';
            } else {
                emptyState.style.display = 'none';
            }
        }
        
        // 添加消息到聊天界面
        function addMessage(text, source) {
            // 如果是部分结果，更新部分结果容器
            if (source === 'partial') {
                partialContainer.innerHTML = '';
                const partialDiv = document.createElement('div');
                partialDiv.className = 'message partial-message';
                partialDiv.textContent = text;
                partialContainer.appendChild(partialDiv);
                return;
            }
            
            // 清空部分结果容器
            if (source === 'user') {
                partialContainer.innerHTML = '';
            }
            
            const messageDiv = document.createElement('div');
            
            // 根据消息来源设置样式
            if (source === 'user') {
                messageDiv.className = 'message user-message';
                messageCount++;
            } else if (source === 'system') {
                messageDiv.className = 'message system-message';
            } else {
                messageDiv.className = 'message assistant-message';
            }
            
            messageDiv.textContent = text;
            
            // 添加时间戳
            const timeSpan = document.createElement('span');
            timeSpan.className = 'message-time';
            timeSpan.textContent = getCurrentTime();
            messageDiv.appendChild(timeSpan);
            
            chatContainer.appendChild(messageDiv);
            chatContainer.scrollTop = chatContainer.scrollHeight;
            
            // 更新空状态显示
            checkEmptyState();
        }
        
        // 更新状态显示
        function updateStatus(status, message) {
            statusIndicator.className = 'status-indicator';
            
            if (status === 'inactive') {
                statusIndicator.classList.add('status-inactive');
                micButton.classList.remove('listening');
                isListening = false;
            } else if (status === 'active') {
                statusIndicator.classList.add('status-active');
            } else if (status === 'listening') {
                statusIndicator.classList.add('status-listening');
                micButton.classList.add('listening');
                isListening = true;
            }
            
            // 添加文本更新动画
            statusMessage.style.opacity = '0';
            setTimeout(() => {
                statusMessage.textContent = message;
                statusMessage.style.opacity = '1';
            }, 300);
        }
        
        // 清空聊天记录
        function clearChat() {
            chatContainer.innerHTML = '';
            partialContainer.innerHTML = '';
            messageCount = 0;
            
            // 恢复空状态
            chatContainer.appendChild(emptyState);
            checkEmptyState();
            
            // 添加系统消息
            addMessage('已清空所有记录', 'system');
            
            // 播放音效
            clickSound.play();
        }
        
        // 保存聊天记录
        function saveChat() {
            if (messageCount === 0) {
                showToast('没有可保存的记录');
                return;
            }
            
            let chatContent = '';
            for (let i = 0; i < chatContainer.children.length; i++) {
                let message = chatContainer.children[i];
                if (message.classList.contains('user-message')) {
                    // 去掉时间戳
                    chatContent += message.textContent.replace(/\d{2}:\d{2}:\d{2}$/, '').trim() + '\n';
                }
            }
            
            if (chatContent.trim() === '') {
                showToast('没有可保存的记录');
                return;
            }
            
            // 创建下载链接
            const blob = new Blob([chatContent], { type: 'text/plain;charset=utf-8' });
            const a = document.createElement('a');
            const date = new Date();
            const fileName = `语音识别记录_${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}.txt`;
            a.href = URL.createObjectURL(blob);
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            
            addMessage(`已保存记录至 ${fileName}`, 'system');
            
            // 播放音效
            clickSound.play();
        }
        
        // 显示提示信息
        function showToast(message) {
            const toast = document.createElement('div');
            toast.textContent = message;
            toast.style.position = 'fixed';
            toast.style.bottom = '20px';
            toast.style.left = '50%';
            toast.style.transform = 'translateX(-50%)';
            toast.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
            toast.style.color = '#fff';
            toast.style.padding = '12px 20px';
            toast.style.borderRadius = '25px';
            toast.style.zIndex = '1000';
            toast.style.transition = 'all 0.3s';
            toast.style.opacity = '0';
            
            document.body.appendChild(toast);
            
            setTimeout(() => {
                toast.style.opacity = '1';
            }, 10);
            
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => {
                    document.body.removeChild(toast);
                }, 300);
            }, 3000);
        }
        
        // 发送WebSocket消息
        function sendWebSocketMessage(data) {
            if (socket && socket.readyState === WebSocket.OPEN) {
                try {
                    socket.send(JSON.stringify(data));
                } catch (error) {
                    console.error('发送WebSocket消息失败:', error);
                    showToast('发送消息失败，请刷新页面重试');
                }
            } else {
                console.error('WebSocket未连接，无法发送消息');
                showToast('服务连接已断开，请刷新页面重试');
            }
        }
        
        // 检测浏览器是否支持语音识别
        function checkBrowserSupport() {
            if (!('WebSocket' in window)) {
                showToast('您的浏览器不支持WebSocket，请更换浏览器');
                return false;
            }
            return true;
        }
        
        // 麦克风按钮点击事件
        micButton.addEventListener('click', function() {
            // 播放音效
            clickSound.play();
            
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                showToast('服务连接已断开，正在尝试重新连接');
                connectWebSocket(); // 尝试重新连接
                return;
            }
            
            if (!isListening) {
                // 发送开始监听的消息
                sendWebSocketMessage({action: 'start_listening'});
                // 添加视觉反馈
                micButton.classList.add('listening');
            } else {
                // 发送停止监听的消息
                sendWebSocketMessage({action: 'stop_listening'});
                partialContainer.innerHTML = '';
                // 添加视觉反馈
                micButton.classList.remove('listening');
            }
        });
        
        // 清空按钮点击事件
        clearButton.addEventListener('click', clearChat);
        
        // 保存按钮点击事件
        saveButton.addEventListener('click', saveChat);
        
        // 页面卸载前关闭WebSocket连接
        window.addEventListener('beforeunload', function() {
            if (socket) {
                socket.close();
            }
        });
        
        // 添加初始系统消息
        window.addEventListener('load', function() {
            // 检查浏览器支持
            if (checkBrowserSupport()) {
                // 初始连接
                connectWebSocket();
                
                // 检查空状态
                checkEmptyState();
            }
        });
    </script>
    <script src="/static/scripts.js"></script>
</body>
</html> 